﻿#region using
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.ComponentModel;

using Microsoft.VisualStudio;
// using Microsoft.VisualStudio.TextTemplating.VSHost;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.Win32;

using XmlGenSharp.Logic.Main;
#endregion

namespace XmlGenSharp.App.VSCustomTool
{
  public abstract class VsMultipleFileGenerator : IVsSingleFileGenerator, IObjectWithSite
  {
    // Private Member Variables

    #region Private Member Variables
    private object site;
    private ServiceProvider serviceProvider = null;

    private string bstrInputFileContents;
    private string wszInputFilePath;
    private EnvDTE.Project project;

    private List<string> newFileNames;
    #endregion

    // Constructors

    #region VsMultipleFileGenerator()
    public VsMultipleFileGenerator()
    {
      EnvDTE.DTE dte = (EnvDTE.DTE)Package.GetGlobalService(typeof(EnvDTE.DTE));
      DTE = dte;
      Array ary = (Array)dte.ActiveSolutionProjects;
      if (ary.Length > 0)
      {
        project = (EnvDTE.Project)ary.GetValue(0);

      }
      newFileNames = new List<string>();
    }
    #endregion

    // Protected Properties

    #region DTE
    protected EnvDTE.DTE DTE
    {
      get;
      set;
    }
    #endregion

    #region Project
    protected EnvDTE.Project Project
    {
      get
      {
        return project;
      }
    }
    #endregion

    #region InputFileContents
    protected string InputFileContents
    {
      get
      {
        return bstrInputFileContents;
      }
    }
    #endregion

    #region InputFilePath
    protected string InputFilePath
    {
      get
      {
        return wszInputFilePath;
      }
    }
    #endregion

    #region PrimaryGenerator
    protected IGenerator PrimaryGenerator
    {
      get;
      set;
    }
    #endregion

    #region SecondaryGenerators
    protected IList<IGenerator> SecondaryGenerators
    {
      get
      {
        return _secondaryGenerators;
      }
      set
      {
        _secondaryGenerators = value;
      }
    }
    private IList<IGenerator> _secondaryGenerators = null;
    #endregion

    #region SolutionDir
    protected string SolutionDir
    {
      get
      {
        return Path.GetDirectoryName(DTE.Solution.FullName);
      }
    }
    #endregion

    #region BindingAnalyzer
    protected BindingAnalyzer BindingAnalyzer
    {
      get;
      set;
    }
    #endregion

    // Private Properties

    #region SiteServiceProvider
    private ServiceProvider SiteServiceProvider
    {
      get
      {
        if (serviceProvider == null)
        {
          Microsoft.VisualStudio.OLE.Interop.IServiceProvider oleServiceProvider = site as Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
          serviceProvider = new ServiceProvider(oleServiceProvider);
        }
        return serviceProvider;
      }
    }
    #endregion

    // Abstract Methods

    #region GetDefaultExtension()
    public abstract string GetDefaultExtension();
    #endregion

    #region DefaultExtension(out string pbstrDefaultExtension)
    public virtual int DefaultExtension(out string pbstrDefaultExtension)
    {
      pbstrDefaultExtension = "Generator.cs";
      return pbstrDefaultExtension.Length;
    }
    #endregion

    // Public Methods

    #region Generate(string wszInputFilePath, string bstrInputFileContents, ...)
    public virtual int Generate(string wszInputFilePath, string bstrInputFileContents,
      string wszDefaultNamespace, IntPtr[] rgbOutputFileContents,
      out uint pcbOutput, IVsGeneratorProgress pGenerateProgress)
    {
      this.bstrInputFileContents = bstrInputFileContents;
      this.wszInputFilePath = wszInputFilePath;
      this.newFileNames.Clear();

      int iFound = 0;
      uint itemId = 0;
      EnvDTE.ProjectItem item;
      Microsoft.VisualStudio.Shell.Interop.VSDOCUMENTPRIORITY[] pdwPriority = new Microsoft.VisualStudio.Shell.Interop.VSDOCUMENTPRIORITY[1];

      // obtain a reference to the current project as an IVsProject type
      Microsoft.VisualStudio.Shell.Interop.IVsProject VsProject = VsHelper.ToVsProject(project);

      // this locates, and returns a handle to our source file, as a ProjectItem
      VsProject.IsDocumentInProject(InputFilePath, out iFound, pdwPriority, out itemId);

      // if our source file was found in the project (which it should have been)
      if (iFound != 0 && itemId != 0)
      {
        Microsoft.VisualStudio.OLE.Interop.IServiceProvider oleSp = null;
        VsProject.GetItemContext(itemId, out oleSp);
        if (oleSp != null)
        {
          ServiceProvider sp = new ServiceProvider(oleSp);
          // convert our handle to a ProjectItem
          item = sp.GetService(typeof(EnvDTE.ProjectItem)) as EnvDTE.ProjectItem;
        }
        else
          throw new ApplicationException("Unable to retrieve Visual Studio ProjectItem");
      }
      else
        throw new ApplicationException("Unable to retrieve Visual Studio ProjectItem");

      // now we can start our work, iterate across all the secondary file generators 
      foreach (IGenerator element in this.SecondaryGenerators)
      {
        try
        {
          // obtain a name for this target file
          string fileName = element.GetFileName();
          // add it to the tracking cache
          newFileNames.Add(fileName);
          // fully qualify the file on the filesystem
          string strFile = Path.Combine(Path.GetDirectoryName(wszInputFilePath), fileName);

          // skip file if it exists and the OverwriteIfExists property is false
          if (File.Exists(strFile) && !element.OverwriteIfExists)
            continue;

          // create the file
          FileStream fs = File.Create(strFile);

          try
          {
            // generate our target file content
            byte[] data = element.GenerateContent(BindingAnalyzer.Config);

            // write it out to the stream
            fs.Write(data, 0, data.Length);

            fs.Close();

            // add the newly generated file to the solution, as a child of the source file...
            EnvDTE.ProjectItem itm = item.ProjectItems.AddFromFile(strFile);
            /*
             * Here you may wish to perform some addition logic
             * such as, setting a custom tool for the target file if it
             * is intented to perform its own generation process.
             * Or, set the target file as an 'Embedded Resource' so that
             * it is embedded into the final Assembly.
                         
            EnvDTE.Property prop = itm.Properties.Item("CustomTool");
            //// set to embedded resource
            itm.Properties.Item("BuildAction").Value = 3;
            if (String.IsNullOrEmpty((string)prop.Value) || !String.Equals((string)prop.Value, typeof(AnotherCustomTool).Name))
            {
                prop.Value = typeof(AnotherCustomTool).Name;
            }
            */
          }
          catch (Exception)
          {
            fs.Close();
            if (File.Exists(strFile))
              File.Delete(strFile);
          }
        }
        catch (Exception ex)
        {
          throw ex;
        }
      }

      // perform some clean-up, making sure we delete any old (stale) target-files
      foreach (EnvDTE.ProjectItem childItem in item.ProjectItems)
      {
        if (!(childItem.Name.EndsWith(GetDefaultExtension()) || newFileNames.Contains(childItem.Name)))
          // then delete it
          childItem.Delete();
      }

      // generate our primary content for our 'single' file
      byte[] summaryData = PrimaryGenerator.GenerateContent(BindingAnalyzer.Config);

      if (summaryData == null)
      {
        // rgbOutputFileContents[0] = 0;

        pcbOutput = 0;
      }
      else
      {
        // return our summary data, so that Visual Studio may write it to disk.
        int length = summaryData.Length;
        rgbOutputFileContents[0] = Marshal.AllocCoTaskMem(length);
        Marshal.Copy(summaryData, 0, rgbOutputFileContents[0], length);
        pcbOutput = (uint)length;
      }

      return VSConstants.S_OK;
    }
    #endregion

    // IObjectWithSite Implementation

    #region GetSite(ref Guid riid, out IntPtr ppvSite)
    public void GetSite(ref Guid riid, out IntPtr ppvSite)
    {
      if (this.site == null)
      {
        throw new Win32Exception(-2147467259);
      }

      IntPtr objectPointer = Marshal.GetIUnknownForObject(this.site);

      try
      {
        Marshal.QueryInterface(objectPointer, ref riid, out ppvSite);
        if (ppvSite == IntPtr.Zero)
        {
          throw new Win32Exception(-2147467262);
        }
      }
      finally
      {
        if (objectPointer != IntPtr.Zero)
        {
          Marshal.Release(objectPointer);
          objectPointer = IntPtr.Zero;
        }
      }
    }
    #endregion

    #region SetSite(object pUnkSite)
    public void SetSite(object pUnkSite)
    {
      this.site = pUnkSite;
    }
    #endregion
  }
}
